<?php

namespace Modules\DataCheck\Excel\Import;

use Illuminate\Support\Facades\DB;
use Modules\Common\Support\Helper;
use Modules\DataCheck\Enums\ErrorEnum;
use Modules\DataCheck\Enums\TaskSourceFileEnum;
use Modules\DataCheck\Excel\Tratis\Excel;
use Modules\DataCheck\Exceptions\DataCheckException;
use Modules\DataCheck\Exceptions\ImportSourceFileException;
use Modules\DataCheck\Models\DcTaskSourceData;
use Modules\DataCheck\Models\DcTaskSourceFile;
use Illuminate\Support\Facades\Log;

/**
 * 源据源文件导入
 */
class SourceFileImport
{
    use Excel;

    /**
     * 开始行
     * @var int
     */
    private int $startLine = 3;

    private string $realFile = '';

    /**
     * 字段映射
     * @var array
     */
    private array $fieldMap = [
        1 => 'code', //单证编号
        2 => 'version', //单证版本
        3 => 'name',  //单证名称,
        4 => 'warehouse', //单证仓库,
        5 => 'serial_num_start', //开始流水号
        6 => 'serial_num_end', //结束流水号
        7 => 'num', //数量
        8 => 'check_state', //状态
        9 => 'org', //领用人/机构
        10 => 'purpose', //用途
    ];

    /**
     * 导入
     * @param DcTaskSourceFile $fileModel
     * @param bool             $forceImport 是否强制导入
     * @return void
     */
    public function handle(DcTaskSourceFile $fileModel, bool $forceImport = false): void
    {
       try {
           //查询最新的状态
           $model = DcTaskSourceFile::query()->find($fileModel->id);
           if (empty($model)) {
               throw new ImportSourceFileException('任务不存在', ErrorEnum::IMPORT_SOURCE_FILE);
           }

           //获取文件完整路径
           $file = Helper::getUploadReaPath($model->file_path);
           if (empty($file) || !file_exists($file)) {
               throw new ImportSourceFileException('原始表文件不存在', ErrorEnum::IMPORT_SOURCE_FILE);
           }

           $this->realFile = $file;

           //如果是导入中，即使强制导入也不允许导入
           if ($model->file_state == TaskSourceFileEnum::RUNNING->value) {
               echo "正在导入中，请勿重复导入\n";
               return;
           }

           //如果不是待导入的状态 且 不是强制导入
           if ($model->file_state != TaskSourceFileEnum::WAIT->value && !$forceImport) {
               return;
           }

           $this->import($model, $forceImport);
       } catch (\Throwable $throwable) {
           $errorMsg = '原表excel文件解析异常';
           if ($throwable instanceof ImportSourceFileException || $throwable instanceof DataCheckException) {
               $errorMsg = $throwable->getMessage();
           }

           //更新为导入失败状态
           $update = [
               'updated_at' => time(),
               'file_state' => TaskSourceFileEnum::FAILED->value,
               'remark' => json_encode([
                   'msg' => $errorMsg,
                   'file_path' => $fileModel->file_path,
                   'error' => [
                       'msg' => $throwable->getMessage(),
                       'file' => $throwable->getFile(),
                       'line' => $throwable->getLine(),
                   ],
               ], JSON_UNESCAPED_UNICODE),
           ];
           DcTaskSourceFile::query()->where('id', $fileModel->id)->update($update);

           //记录日志
           $log = [
               'msg' => $errorMsg,
               'file_id' => $fileModel->id,
               'file_path' => $this->realFile ?: $fileModel->file_path,
               'error' => [
                   'msg' => $throwable->getMessage(),
                   'file' => $throwable->getFile(),
                   'line' => $throwable->getLine(),
               ],
           ];
           Log::error(json_encode($log, JSON_UNESCAPED_UNICODE));
       }
    }

    /**
     * 导入
     * @param DcTaskSourceFile $fileModel
     * @param bool             $force
     * @return void
     * @throws \Exception
     */
    private function import(DcTaskSourceFile $fileModel, bool $force): void
    {
        echo "开始导入 file_id:{$fileModel->id}, file_path: {$this->realFile}...\n";

        //修改为执行中状态
        $res = $fileModel->update([
            'file_state' => TaskSourceFileEnum::RUNNING->value,
            'remark' => '',
            'updated_at' => time(),
        ]);
        if (!$res) {
            throw new ImportSourceFileException('更新原表文件解析状态失败', ErrorEnum::IMPORT_SOURCE_FILE);
        }

        $pathInfo = pathinfo($this->realFile);
        $reader  = new \Vtiful\Kernel\Excel(['path' => $pathInfo['dirname']]);
        $reader->openFile($pathInfo['basename'])->openSheet();

        DB::transaction(function () use ($reader, $fileModel, $force) {
            if ($force) {
                echo "强制导入，正在删除原数据...\n";
                // 删除原来导入的数据
                DcTaskSourceData::query()->where('file_id', $fileModel->id)->delete();
                echo "删除完成...\n\n";
            }

            $line = $total = 0;
            $data = [];
            $startTime = microtime(true);
            while (($row = $reader->nextRow()) !== null &&  array_filter($row) !== []) {
                $line++;
                if ($line < $this->startLine) {//跨表头
                    continue;
                }

                //数据格式转换
                $lineData = $this->transDataT($this->fieldMap, $row, [], false, $line);

                // 格式检测
                $this->checkFormat($lineData, $line);

                $data[] = [
                    'file_id' => $fileModel->id,
                    'serial_num_start' => intval($lineData['serial_num_start']),
                    'serial_num_end' => intval($lineData['serial_num_end']),
                    'check_state' => $lineData['check_state'],
                    'num' => intval($lineData['num']),
                    'line_data' => json_encode($lineData, JSON_UNESCAPED_UNICODE),
                    'created_at' => time(),
                    'updated_at' => time(),
                ];

                //大于 100 条数据批量插入
                if (count($data) > 100) {
                    DcTaskSourceData::insert($data);
                    unset($data);
                    $data = [];
                }

                $total++;

                if ($total % 1000 == 0) {
                    echo "已导入行数：" . $total . PHP_EOL;
                }
            }

            //批量插入剩余数据
            if (!empty($data)) {
                DcTaskSourceData::insert($data);
            }

            //更新导入状态
            $update = [
                'file_state' => TaskSourceFileEnum::SUCCESS->value,
                'updated_at' => time(),
                'remark' => '',
            ];
            DcTaskSourceFile::query()->where('id', $fileModel->id)->update($update);

            $endTime = microtime(true);

            echo "导入完成，耗时：" . ($endTime - $startTime) . '秒，导入行数：' . $total . PHP_EOL;
        });
    }

    /**
     * 数据格式校验
     * @param array $data
     * @param int   $line
     * @return void
     */
    private function checkFormat(array $data, int $line): void
    {
        if (empty($data['serial_num_start'])) {
            throw new ImportSourceFileException("第{$line}行开始流水号不能为空", ErrorEnum::IMPORT_SOURCE_FILE);
        }

        if (empty($data['serial_num_end'])) {
            throw new ImportSourceFileException("第{$line}行结束流水号不能为空", ErrorEnum::IMPORT_SOURCE_FILE);
        }

        if ($data['serial_num_start'] > $data['serial_num_end']) {
            throw new ImportSourceFileException("第{$line}行结束流水号不能小于开始流水号", ErrorEnum::IMPORT_SOURCE_FILE);
        }

        if (empty($data['check_state'])) {
            throw new ImportSourceFileException("第{$line}行状态不能为空", ErrorEnum::IMPORT_SOURCE_FILE);
        }
    }
}